﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace Bettzueche.Dampfross
{
    class Program
    {
                
        static void GeneriereKarte() {
            ////var hexKarte = Karte.RandomMap(rows: 9, cols: 10); //(7+2)*(8+2)
            //var hexKarte = Karte.RandomMap(rows: 14, cols: 16); //(12+2)*(14+2)  
            ////var hexKarte = Karte.LoadMapFromFile(@"Karten\hexMap9x10-02.bin");
            //Karte karte = new Karte(hexKarte);
            //karte.Ziel = new HexKoordinate(3, 8);
            //karte.SafeMapToFile(@"Karten\hexMap14x16-01.bin");

            var hexKarte = Karte.HomoMap(rows: 14, cols: 16); //(12+2)*(14+2)
            Karte karte = new Karte(hexKarte);
            karte.Ziel = new HexKoordinate(3, 8);
            karte.SafeMapToFile(@"Karten\hexMap14x16-02.bin");
        }

        
        /// <summary>
        /// Speichert zu jedem Zustand die Soll-Kosten aller Aktionen als CSV: nr;zid;NO;N;NW;SW;S;SO.
        /// </summary>
        /// <param name="karte">Die initialisierte Karte (mit Ziel-Koordinate!)</param>
        /// <param name="zCount">Anzahl der Zustände / Sichtfelder in der Karte (inkl. Stadt- und innere Wasser-Felder)</param>
        /// <param name="zRows">Anzahl Zustände je Reihe (ZickZack)</param>
        /// <param name="zIDs">Array mit den korrespondierenden Zustandsnummern.</param>
        /// <param name="filename">Name der Datei, in die die Sollwerte gespeichert werden.</param>
        static void SpeicherSollwerte(Karte karte, int zCount, int zRows, int[] zIDs, string filename) {
            int[][] sollwerte = GibSollwerte(karte, zCount, zRows);
            //int[] zIDs = LadeZIDs(@"Karten\zIDs9x10-01.txt");
            var csv = new StringBuilder("nr;id;NO;N;NW;SW;S;SO");
            csv.AppendLine();
            for (int i = 0; i < sollwerte.Length; i++) {
                csv.AppendFormat("{0};{1};", i, zIDs[i]);
                for (int dir = 0; dir < sollwerte[i].Length; dir++) {
                    csv.AppendFormat("{0};", sollwerte[i][dir]);
                }
                csv.AppendLine();
            }
            //File.WriteAllText(@"Karten\Sollwerte9x10-01.csv", csv.ToString());
            File.WriteAllText(filename, csv.ToString());
        }

        static void Main(string[] args) {
            Console.WriteLine("----- Dampfross -----");
            //GeneriereKarte();

            //ZustandsnummernSpeichern(@"Karten\hexMap14x16-02.bin", new HexKoordinate(3,8));

            var hexKarte = Karte.LoadMapFromFile(@"Karten\hexMap14x16-02.bin");
            Karte karte = new Karte(hexKarte);
            karte.Ziel = new HexKoordinate(3, 8);
            int[] zIds = Experiment.LadeZIDs(@"Karten\zIDs14x16-02.txt");
            SpeicherSollwerte(karte, (14-2)*(16-2), (14-2), zIds, @"Karten\Sollwerte14x16-02.csv");

            //RunParameterTests();
            //RunTwoWorldsTest();
            Console.Write("Habe fertig.");
            Console.ReadKey();
        }

        static void RunTwoWorldsTest() {
            Experiment2 experiment = new Experiment2();
            //Experiment2b experiment = new Experiment2b();
            bool loadSucc = experiment.LadeTestUmgebungen(Experiment.Karten.Map9x10_1, Experiment.Karten.Map9x10_2);
            if (!loadSucc) {
                Console.WriteLine("Error beim Laden der Testumgebung");
                Console.WriteLine("Beliebige Taste zum Beenden des Programms");
                Console.ReadKey();
            }

            // Lernparameter nach Anaylse von RunParameterTests
            bool initSucc = experiment.SetzteLernparameter(epsilon: 0.03, alpha: 0.3, gamma: 1.0, lambda: 0.7);
            if (!initSucc) {
                Console.WriteLine("Error beim Setzen der Lernparameter");
                Console.WriteLine("Beliebige Taste zum Beenden des Programms");
                Console.ReadKey();
                return;
            }
            Console.WriteLine("Experiment gestartet...");

            experiment.StarteExperiment(episodes: 2000);

            Console.WriteLine();
            Console.WriteLine("Experiment durchgelaufen und im Ordner \"Experimente\" als csv gespeichert.");
            Console.WriteLine("Beliebige Taste zum Beenden des Programms");
            Console.ReadKey();
        }

        static void RunParameterTests() {
            Experiment experiment = new Experiment();

            bool loadSucc = experiment.LadeTestUmgebung(Experiment.Karten.Map9x10_2);

            if (!loadSucc) {
                Console.WriteLine("Error beim Laden der Testumgebung");
                Console.WriteLine("Beliebige Taste zum Beenden des Programms");
                Console.ReadKey();
            }

            // Bereite Parameter vor
            // Epsilon
            Dictionary<int, double> epsDic = new Dictionary<int, double>();
            epsDic.Add(0, 0.2);
            epsDic.Add(1, 0.1);
            epsDic.Add(2, 0.05);
            epsDic.Add(3, 0.01);
            epsDic.Add(4, 0.0);
            epsDic.Add(5, 0.03);
            for (int i = 6; i < 30; i++) {
                epsDic.Add(i, 0.03);
            }
            // Alpha
            Dictionary<int, double> alphDic = new Dictionary<int, double>();
            for (int i = 0; i < 6; i++) {
                alphDic.Add(i, 0.5);
            }
            alphDic.Add(6, 0.7);
            alphDic.Add(7, 0.5);
            alphDic.Add(8, 0.35);
            alphDic.Add(9, 0.2);
            alphDic.Add(10, 0.1);
            alphDic.Add(11, 0.3);
            for (int i = 12; i < 30; i++) {
                alphDic.Add(i, 0.3);
            }
            // Gamma
            Dictionary<int, double> gamDic = new Dictionary<int, double>();
            for (int i = 0; i < 12; i++) {
                gamDic.Add(i, 0.9);
            }
            gamDic.Add(12, 1.0);
            gamDic.Add(13, 0.95);
            gamDic.Add(14, 0.9);
            gamDic.Add(15, 0.8);
            gamDic.Add(16, 0.7);
            gamDic.Add(17, 0.85);
            for (int i = 18; i < 30; i++) {
                gamDic.Add(i, 0.85);
            }
            // Gamma
            Dictionary<int, double> lamDic = new Dictionary<int, double>();
            for (int i = 0; i < 18; i++) {
                lamDic.Add(i, 0.7);
            }
            lamDic.Add(18, 0.99);
            lamDic.Add(19, 0.95);
            lamDic.Add(20, 0.9);
            lamDic.Add(21, 0.8);
            lamDic.Add(22, 0.7);
            lamDic.Add(23, 0.6);
            lamDic.Add(24, 0.5);
            lamDic.Add(25, 0.4);
            lamDic.Add(26, 0.3);
            lamDic.Add(27, 0.2);
            lamDic.Add(28, 0.1);
            lamDic.Add(29, 0.0);

            for (int i = 0; i < 30; i++) {
                double eps = epsDic[i];
                double alph = alphDic[i];
                double gam = gamDic[i];
                double lam = lamDic[i];
                bool initSucc = experiment.SetzteLernparameter(epsilon: eps, alpha: alph, gamma: gam, lambda: lam);

                if (!initSucc) {
                    Console.WriteLine("Error beim Setzen der Lernparameter");
                    Console.WriteLine("Beliebige Taste zum Beenden des Programms");
                    Console.ReadKey();
                    return;
                }
                Console.WriteLine("Experiment " + i + " gestartet...");
                // Timer oder Statusanzeige
                //TimeSpan duration = new TimeSpan();
                //TimeSpan oneSec = new TimeSpan(0, 0, 1);

                //var aTimer = new System.Timers.Timer(1000);
                //aTimer.Elapsed += (sender, eArg) => {
                //    duration += oneSec;
                //    Console.Write(duration.ToString());
                //    Console.CursorLeft = 0;
                //};
                //aTimer.Start();

                Task expTask = new Task(() => experiment.StarteExperiment(episodes: 1000));
                // StarteExperiment(episodes: 300, passes: 5)

                expTask.Start();
                expTask.Wait();
                //aTimer.Stop();
                Console.WriteLine();
                Console.WriteLine("Experiment " + i + " beendet.");
                //Console.WriteLine("Beliebige Taste zum Beenden des Programms");
                //Console.ReadKey();
            }
            Console.WriteLine("Experimente durchgelaufen und im Ordner \"Experimente\" als csv gespeichert.");
            Console.WriteLine("Beliebige Taste zum Beenden des Programms");
            Console.ReadKey();
        }


        static void AlteMain(string[] args) {

            //ZustandsnummernSpeichern();
            //GeneriereKarte();
            //Experiment.LadeSollwerte(@"Karten\Sollwerte9x10-02.csv");
            //Console.ReadLine();
            
            //var hexKarte = Karte.RandomMap(rows: 5, cols: 6);
            var hexKarte = Karte.LoadMapFromFile(@"Karten\hexMap9x10-02.bin");
            Karte karte = new Karte(hexKarte);
            karte.Ziel = new HexKoordinate(3, 8);
            //karte.SafeMapToFile(@"Karten\hexMap5x6-01.bin");
            //hexKarte = null;
            int[] zIds = Experiment.LadeZIDs(@"Karten\zIDs9x10-02.txt");
            SpeicherSollwerte(karte, (9-2)*(10-2), (9-2), zIds, @"Karten\Sollwerte9x10-02.csv");

            Agent agent = new Agent(karte);


            // Agenten-Parameter:
            agent.SetStates(zIds);
            int[][] sollwerte = GibSollwerte(karte, (9-2)*(10-2), 7);
            // folgende Startfelder haben alle  3 Schritte  und 7 Wegkosten zum Ziel:
            HexKoordinate[] startFelder = { new HexKoordinate(0, 2), new HexKoordinate(2, 1), new HexKoordinate(3, 1) };

            agent.SetSollwerte(sollwerte.Cast<double[]>().ToArray()); // geht das???
            agent.Alpha = 0.25;      // Lernrate
            agent.Epsilon = 0.01;    // Explorationsrate
            //agent.Gamma = 0.95;     // Abschwächungsfaktor
            //agent.Lambda = 0.6;     // Spurzerfallsvektor
            //agent.MinBerechtigung = 0.006; // MinE; etwa 50/10 Schritte zurückreichend

            int steps = 0;
            double dSteps = 0.0;
            double reward = .0;
            double dReward = 0.0;
            int cnt = 0;
            double mse;
            double dMse = 0.0;
            int episoden = 500;
            StringBuilder sb = new StringBuilder();
            sb.AppendFormat("Je {0} Episoden, durchschnittlich:", episoden);
            sb.AppendLine();
            Random random = new Random();
            ConsoleKeyInfo kInfo;

            do {
                for (int j = 0; j < 20; j++) {
                    for (int i = 0; i < episoden; i++) {
                        int start = random.Next(3);
                        karte.ReSpawnAgent(startFelder[start]);
                        agent.RunDebugEpisode(2 * hexKarte.Count, out steps, out reward, out mse);
                        reward = agent.RewardTotal;
                        agent.Reset();
                        cnt++;
                        Console.CursorTop = Console.CursorTop-1;
                        Console.CursorLeft = 0;
                        Console.WriteLine("\nEpisode {2} beendet mit {0} Schritten und Kosten von {1:N1}.\n'q' zum Beenden. Beliebige andere Taste für nächste Episode... ", steps, reward, cnt);
                        dSteps += steps;
                        dReward += reward;
                        dMse += mse;
                    }
                    dSteps /= episoden;
                    dReward /= episoden;
                    dMse /= episoden;
                    //Console.WriteLine("Je {0} Episoden, durchschnittlich:", episoden);
                    sb.AppendFormat("Schritte: {0,7:N3}\tKosten: {1,7:N3}\t\tMSE: {2,7:N3}", dSteps, dReward, dMse);
                    sb.AppendLine();
                    //Console.WriteLine("Schritte: {0,7:N3}\tKosten: {1,7:N3}\tMSE: {2,7:N3}", dSteps, dReward, dMse);
                    Console.WriteLine(sb.ToString());
                }
                kInfo = Console.ReadKey(true);
                //Console.CursorTop = Console.CursorTop-2;
                //Console.CursorLeft = 0;
                //Console.Clear();
            } while (kInfo.Key != ConsoleKey.Q);


            //Console.ReadKey(true);
        }

        /// <summary>
        /// Gibs the sollwerte (minimalen Kosten) je Zustand und für dessen 6 Richtungen.<para>
        /// Die Rand-Wasserfelder, das Zielfeld und die "normalen" Wasserfelder sind dabei ausgespart.
        /// </para>
        /// </summary>
        /// <param name="karte">The karte.</param>
        /// <param name="zCount">Anzahl der Zustände</param>
        /// <param name="zRows">Anzahl der Zeilen (Landfeldzeilen in der Karte)</param>
        /// <returns></returns>
        static int[][] GibSollwerte(Karte karte, int zCount, int zRows) {
            int cols = zCount / zRows;
            List<int[]> resList = new List<int[]>();
            //int[][] result = new int[zCount][]; // 12 Zustände (die 3x4 Landfelder)
            var dijkstaKarte = karte.Dijkstra();
            dijkstaKarte[karte.Ziel].AlleKosten = new int[6]; // Korrektur der Sollwerte an Zielkoordinate
            var enumerator = dijkstaKarte.GetEnumeratorZickZackZeile();
            bool hasNext = false;
            //int idx = 0;
            for (int i = 0; i < (cols+2); i++) { // erste Zeile überspringen
                hasNext = enumerator.MoveNext();
            }
            for (int row = 1; row <= zRows; row++) { // für Zeile 1 - 3...
                hasNext = enumerator.MoveNext(); // ersten in Zeile überspringen
                DijkstraKachel dijKachel = null;
                for (int col = 1; col <= cols; col++) { // 1-4
                    hasNext = enumerator.MoveNext();
                    dijKachel = enumerator.Current;

                    if (dijKachel.Koordinate == karte.Ziel) {
                        // Überspringe Zielfeld - ist irrelevant
                        continue;
                    }
                    /* Wasserfelder überspringen; Wasserfelder zeichenen sich bei DijkstraKachel dadurch aus,
                       dass alle Kosten int-Max sind. */
                    //bool isWater = true;
                    //for (int k = 0; k < dijKachel.AlleKosten.Length; k++) {
                    //    isWater = isWater && (dijKachel.AlleKosten[k] == Int32.MaxValue);
                    //}
                    /* es reicht, den ersten Wert zu prüfen; entweder sind alle Int-Max oder keine */
                    if (dijKachel.AlleKosten[0] == Int32.MaxValue) {
                        continue;
                    }

                    resList.Add(dijKachel.AlleKosten);
                    //result[idx++] = dijKachel.AlleKosten;
                }
                hasNext = enumerator.MoveNext(); // letzten in Zeile überspringen
            }
            
            return resList.ToArray();
            //return result;
        }

        static void ZustandsnummernSpeichern(string kartenDatei, HexKoordinate ziel) {
            //var hexKarte = Karte.LoadMapFromFile(@"Karten\hexMap9x10-02.bin");
            var hexKarte = Karte.LoadMapFromFile(kartenDatei);
            Karte karte = new Karte(hexKarte);

            karte.Ziel = ziel;

            Umgebung sichtFeld = null;
            HexRichtungen richtung;
            int zID;
            StringBuilder sb = new StringBuilder();

            HexKoordinate position = new HexKoordinate();
            HexKoordinate nextPos = new HexKoordinate();
            for (int row = 1; row <= 12/*7*/; row++) {
                position = new HexKoordinate(row, 0);
                for (int col = 0; col <= 13/*7*/; col++) {
                    richtung = (col % 2) == 0 ? HexRichtungen.SO : HexRichtungen.NO;
                    nextPos = HexMapRectFlat.Move(position, richtung);
                    if (hexKarte[nextPos].Content.Typ != Kacheltyp.Wasser) {
                        karte.ReSpawnAgent(nextPos);
                        sichtFeld = karte.AktuellerZustand;
                        zID = Agent.GibZustandsIndex(sichtFeld);
                        sb.AppendFormat("#({0,2}, {1}) = {2}", karte._aktuellePosition.Row, karte._aktuellePosition.Col, zID);
                        sb.AppendLine();
                    }
                    position = nextPos;
                }
            }
            SafeZustandsIDs(sb.ToString());
            
        }

        static void SafeZustandsIDs(string text) {
            using (StreamWriter sw = File.CreateText("zIDs14x16-02.txt")) {
                sw.Write(text);
            }
        }

        /// <summary>
        /// Gibs the soll mittelwerte.
        /// </summary>
        /// <param name="filename">The filename der ungemittelten Sollwerte.</param>
        /// <returns>Key=zID, Value=gemittelte Minimalkosten für Aktion NO, N,...,SO </returns>
        public static Dictionary<int,double[]> GibSollMittelwerte(string filename) {
            Dictionary<int, double[]> sollWerte = new Dictionary<int, double[]>();
            Dictionary<int, int> idCount = new Dictionary<int, int>();
            string fullText = "";
            try {
                //var idLines = File.ReadLines(filename);
                fullText = File.ReadAllText(filename);
            }
            catch (Exception e) {
                Debug.WriteLine("GibSollMittelwerte: Failed to read file. Reason: " + e.Message);
                return null;
            }
            //                      ID  ;  NO  ;  N   ;  NW  ;  SW  ;   S  ;  SO  ;
            Regex rx = new Regex("(\\d+);(\\d+);(\\d+);(\\d+);(\\d+);(\\d+);(\\d+);\r$", RegexOptions.Multiline | RegexOptions.IgnoreCase);// 
            MatchCollection matches = rx.Matches(fullText);
            if (matches.Count > 0) {
                foreach (Match match in matches) {
                    int id = Int32.Parse(match.Groups[1].Value);
                    double[] soll_zArr = new double[6];
                    for (int i = 2; i <= 7; i++) {
                        double soll_i = Double.Parse(match.Groups[i].Value);
                        soll_zArr[i-2] = soll_i;
                    }
                    if (sollWerte.ContainsKey(id)) {
                        double[] soll = sollWerte[id];
                        for (int i = 0; i < soll.Length; i++) {
                            soll[i] += soll_zArr[i];
                        }
                        idCount[id]++;
                    }
                    else {
                        sollWerte[id] = soll_zArr;
                        idCount[id] = 1;
                    }
                    
                }
                // mehrfache Mitteln
                foreach (int id in idCount.Keys) {
                    int count = idCount[id];
                    if (count > 1) {
                        double[] solls = sollWerte[id];
                        for (int i = 0; i < solls.Length; i++) {
                            solls[i] = solls[i] / count;
                        }
                    }
                }
            }
            return sollWerte;
        }

    }

}
